﻿using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using BusinessLogic.Models.LogicModels.Security;
using Framework.Email;
using Framework.Email.EmailModels;
using Framework.Exceptions;
using Framework.Exceptions.BusinessExceptions;
using Interfaces.BusinessLogic;
using Interfaces.Enums;
using Interfaces.Infrastructure;
using Interfaces.Models;
using Interfaces.Repositories;
using Interfaces.RepositoryModels.InternalModels.UserRoleModels;
using Interfaces.Validation;

namespace BusinessLogic.Services.Operations
{
    public class UserOperationService : IUserOperationService
    {
        private readonly IValidator _validator;
        private readonly IUserRepository _userRepository;
        private readonly IRoleRepository _roleRepository;
        private readonly IHashService _hashService;
        private readonly IPasswordGeneratedService _passwordGeneratedService;
        private readonly IEmailService _emailService;
        private readonly IEntityMapper<IUser, UserModel> _inMapper;
        private readonly IEntityMapper<UserModel, IUser> _outMapper; 

        public UserOperationService(IValidator validator, 
            IUserRepository userRepository, 
            IRoleRepository roleRepository,
            IHashService hashService, 
            IEntityMapper<IUser, UserModel> inMapper, 
            IEntityMapper<UserModel, IUser> outMapper, 
            IPasswordGeneratedService passwordGeneratedService, 
            IEmailService emailService)
        {
            _validator = validator;
            _userRepository = userRepository;
            _hashService = hashService;
            _inMapper = inMapper;
            _outMapper = outMapper;
            _passwordGeneratedService = passwordGeneratedService;
            _emailService = emailService;
            _roleRepository = roleRepository;
        }

        public IUser CreateUser(string login, string password, string email, IEnumerable<RoleType> roles)
        {
            var newLogicUser = Create(login, password, email, roles);
            
            var newRepoUser = _inMapper.Map(newLogicUser);

            ExceptionHandlingManager.Process(() => _userRepository.Create(newRepoUser), PolicyNames.DALPolicy);
          
            return newLogicUser;

        }

        public void ResetPassword(Guid userId)
        {
            try
            {
                var user = _userRepository.GetById(userId);
                
                if(user == null)
                    throw new EntityNotExists("User");

                var newPassword = _passwordGeneratedService.Generate();

                user.Password = _hashService.GetHash(newPassword);

                _userRepository.Update(user);

                //todo replace new operator
                var emailModel = new PasswordResetModel {NewPassword = newPassword, UserName = user.UserName};
                
                _emailService.Send(user.Email, emailModel, EmailTemplatesName.ResetPassword);

            }
            catch (Exception exception)
            {
                if(ExceptionHandlingManager.HandleException(exception, PolicyNames.DALPolicy))
                  throw;
            }
        }

        public IUser GetUser(string login, string email, string password)
        {
            try
            {
                var result = GetUserModel(login, email, password);

                var user = Mapper.DynamicMap<UserModel, IUser>(result);

                return user;
            }
            catch (Exception exception)
            {
                if (ExceptionHandlingManager.HandleException(exception, PolicyNames.DALPolicy))
                    throw;
            }

            return null;
        }

        public void Remove(Guid id)
        {
           ExceptionHandlingManager.Process(() => _userRepository.Delete(id), PolicyNames.DALPolicy);
        }

        public void AddRole(Guid userId, RoleType roleType)
        {
            try
            {
                var user = _userRepository.GetById(userId);
                if (!user.Roles.Any(x => x.Type.Equals(roleType)))
                {
                    var role = _roleRepository.GetByType(roleType);
                    user.Roles.Add(role);
                    _userRepository.Update(user);
                }
                else
                    throw new EntityAlreadyExsistsException(string.Format("Role {0} already exists for user with id={1}", roleType, userId));
            }
            catch (Exception exception)
            {
                if (ExceptionHandlingManager.HandleException(exception, PolicyNames.DALPolicy)) throw;
            }
            
        }

        public void RemoveRole(Guid userId, RoleType roleType)
        {
            try
            {
                var user = _userRepository.GetById(userId);
                if (user.Roles.Any(x => x.Type.Equals(roleType)))
                {
                    var role = _roleRepository.GetByType(roleType);
                    user.Roles.Remove(role);
                    _userRepository.Update(user);
                }
                else throw
                    new EntityNotExists(string.Format("Role {0} not exists for user with id={1}", roleType, userId));
            }
            catch (Exception exception)
            {
                if (ExceptionHandlingManager.HandleException(exception, PolicyNames.DALPolicy)) throw;
            }
           
        }

        public void UpdatePassword(string login, string email, string oldPassword, string newPassword)
        {
            try
            {
                var user = GetUserModel(login, email, oldPassword);

                user.Password = _hashService.GetHash(newPassword);

                _userRepository.Update(user);
            }
            catch (Exception exception)
            {
                if (ExceptionHandlingManager.HandleException(exception, PolicyNames.DALPolicy))
                    throw;
            }
           
        }

        public IEnumerable<IUser> GetAllUsers()
        {
                return
                    ExceptionHandlingManager.Process(
                        () => from user in _userRepository.GetAll() select _outMapper.Map(user), PolicyNames.DALPolicy);
        }

        public IValidationResult Validate(string login, string password, string email, IEnumerable<RoleType> roles, string rulesetName = null)
        {
            var newLogicUser = Create(login, password, email, roles);

            return ExceptionHandlingManager.Process(() => _validator.Validate(newLogicUser, rulesetName), PolicyNames.DALPolicy);
        }

        public bool VerifyPassword(string enteredPassword, string hashedPassword)
        {
            var result = false;

            try
            {
                var passwordHash = _hashService.GetHash(enteredPassword);

                result = passwordHash.Equals(hashedPassword, StringComparison.OrdinalIgnoreCase);
            }
            catch (Exception exception)
            {
                if (ExceptionHandlingManager.HandleException(exception, PolicyNames.DALPolicy))
                    throw;
            }

            return result;
        }

        #region utility

        private IUser Create(string login, string password, string email, IEnumerable<RoleType> roles = null)
        {
            
            if(roles == null)
                roles = new List<RoleType>();
            
            var newUser = new User
            {
                Id = Guid.NewGuid(),
                Email = email,
                Password = _hashService.GetHash(password),
                UserName = login
            };

            var users = new List<IUser> { newUser };

            var logicRoles = roles.Select(x => (new Role
                {
                    Type = x,
                    Id = Guid.NewGuid(),
                    Users = users
                }) as IRole).ToList();

            newUser.Roles = logicRoles;
            return newUser;
        }

        private UserModel FindByLoginOrEmail(string login, string email)
        {
            UserModel result = null;

            try
            {
                if (login != null)
                    result = _userRepository.FindByLogin(login);
                result = result ?? _userRepository.FindByEmail(email);

            }
            catch (Exception exception)
            {
                if (ExceptionHandlingManager.HandleException(exception, PolicyNames.DALPolicy)) throw;
            }
            return result;
        }

        private UserModel GetUserModel(string login, string email, string password)
        {
            if (login == null && email == null)
                throw new ArgumentException("login or email should has some value");

            if (password == null)
                throw new ArgumentNullException("password");

            var result = FindByLoginOrEmail(login, email);

            if (result == null)
                throw new ArgumentValidationException("Incorrect login or email");

            if (!VerifyPassword(password, result.Password))
                throw new ArgumentValidationException("Incorrect password");

            return result;
        }

        #endregion
    }
}
